import lodash from 'lodash'
import { ContactModel } from './ContactModel'
import { MaterialModel } from './MaterialModel'
import { OrderItemModel } from './OrderItemModel'
import { OrderModel } from './OrderModel'
import Api from '~/api'

const SEP = '!@#$%'

interface WithReason extends OrderItemModel {
  reason: string
}

export interface QuantityWithWeight {
  quantity: number
  weight: number
}

function mergeAndSort(a: OrderItemModel[], b: OrderItemModel[]) {
  const arr = [] as OrderItemModel[]
  a.forEach(x => arr.push(x))

  // 如果没有，才添加。如果有，quantity相加
  b.forEach((x) => {
    // 查找存在的
    const oim = arr.find(y => y.material!.id === x.material!.id && y.unitPrice === x.unitPrice)
    if (oim) {
      oim.quantity += x.quantity
    }
    else {
      arr.push(x)
    }
  })
  arr.sort((a, b) => b.quantity - a.quantity)

  // 同种商品，单价不同，只保留更常用的选项
  const ret = [] as OrderItemModel[]
  arr.forEach((oim) => {
    if (!ret.find(x => x.material?.id === oim.material?.id)) {
      ret.push(oim)
    }
  })
  return ret
}

function sameItem(a?: OrderItemModel, b?: OrderItemModel) {
  return (
    a && b
    && a.material!.id === b.material!.id
    && a.unitPrice === b.unitPrice
  )
}

function clearWeight(arr: OrderItemModel[]) {
  arr.forEach(x => x.quantity = 0)
}

export class ItemRecommendModel {
  private allOrders: OrderModel[] = []
  private allMaterials: MaterialModel[] = []

  /**
   *
   */
  constructor(public consumer: ContactModel) {
    // ...
  }

  async init() {
    const [{ objects: orders }, materials] = await Promise.all([
      Api.Order.getPaged(1, 100, {
        consumer: this.consumer.id.toString(),
      }),
      Api.Material.getAll(),
    ])
    this.allOrders = orders
    this.allMaterials = materials
  }

  /**
   *
   * @returns 返回在商品编辑页面创建的商品
   */
  private getItemsFromMaterials(): OrderItemModel[] {
    return this.allMaterials.map(x => OrderItemModel.fromMaterialModel(x))
  }

  /**
   * 所有的可选商品：
   * 1. 物料中配置的
   * 2. 订单中售出的
   */
  // private getAllItems(): OrderItemModel[] {
  //   /**
  //    * arr存储所有可能售卖的商品，并且进行引用计数
  //    */
  //   const arr = [] as OrderItemModel[]
  //   const addRef = (x: OrderItemModel) => {
  //     const existItem = arr.find(y => y.material!.id === x.material!.id && y.unitPrice === x.unitPrice)
  //     if (existItem) {
  //       existItem.quantity += 1
  //     }
  //     else {
  //       arr.push(x)
  //     }
  //   }
  //   this.allMaterials.forEach((material) => {
  //     const oim = new OrderItemModel()
  //     oim.material!.id = material.id
  //     oim.unitPrice = material.unitPrice
  //     addRef(oim)
  //   })

  //   arr.sort((a, b) => b.quantity - a.quantity)
  //   arr.forEach(x => x.quantity = 0)
  //   return arr
  // }

  /**
   * 为客户创建商品序列
   * 为了快速的创建订单，对可能选择的商品进行智能推荐。
   * 判断依据：历史订单中，出现在该位置最多的商品。其中位置信息包含：
   * 1. 商品的次序
   * 2. 商品的前序链
   * 3. 订单的客户
   *
   * @param consumer 消费者
   * @param index 商品在订单中的次序
   */
  public async recommand(location: number, preChain: OrderItemModel[], consumer: ContactModel | undefined): Promise<WithReason[]> {
    console.log(`recommand(location: ${location}, preChain: ${preChain}, consumer: ${consumer?.id})`)

    /**
     *
     * @param order 订单
     * @param i 次序
     * @returns 订单中位列某个次序的商品的特征字符串
     */
    const pickItem = (order: OrderModel, i: number): string => {
      const item = order.items[i]
      if (!item) {
        return ''
      }
      else {
        return `${item.material!.id}${SEP}${item.unitPrice}`
      }
    }

    /**
     * 给订单对某次推荐打分
     * @param order 要积分的订单
     * @param location 推荐位置的次序
     * @param preChain 推荐位置的前序商品
     * @param consumer 要推荐的订单属于谁
     */
    const getRank = (
      order: OrderModel,
      location: number,
      preChain: OrderItemModel[],
      consumer: ContactModel | undefined,
    ): number => {
      const sameConsumerFactor = order.consumer && order.consumer?.id === consumer?.id ? 100000000 : 1

      let chainFactor = 1
      for (let i = 0; i < preChain.length; i++) {
        const pre = preChain[i] // 第 i 个前序商品
        if (sameItem(order.items[location - 1 - i], pre)) {
          chainFactor *= 10
        }
        else {
          break
        }
      }
      // console.log(`factor: ${sameConsumerFactor} ${chainFactor}`)

      return sameConsumerFactor * chainFactor
    }

    // 对所有订单进行打分
    class Ranked extends OrderModel {
      rank = 0
    }

    console.log(this.allOrders)

    this.allOrders.forEach((order) => {
      (order as any).rank = getRank(order, location, preChain, consumer)
      console.log((order as any).rank)
    })

    const rankedArr = this.allOrders as Ranked[]

    console.log(rankedArr)

    // 对订单的推荐商品进行分组
    const mapItemRankedArr = lodash.groupBy(rankedArr, ranked => pickItem(ranked, location))
    delete mapItemRankedArr['']

    // console.log('---!!!!');
    console.log(mapItemRankedArr)

    // 创建oim推荐列表
    const fromOrder = Object.keys(mapItemRankedArr).map<OrderItemModel>((key) => {
      const oim = new OrderItemModel()
      const [id, unitPrice] = key.split(SEP)
      oim.material = this.allMaterials.find(x => x.id === Number.parseInt(id))
      oim.unitPrice = Number.parseFloat(unitPrice)
      oim.quantity = mapItemRankedArr[key].map(x => x.rank).reduce((y, x) => y + x, 0)
      return oim
    })

    console.log(fromOrder)

    const ret = mergeAndSort(fromOrder, this.getItemsFromMaterials())
    const reasons = ret.map(x => `Rank ${x.quantity}`)

    // console.log(JSON.parse(JSON.stringify(ret)))
    clearWeight(ret)

    // const reasonAdded = ret.map((x, i) => ({ ...x, reason: reasons[i] }))
    const reasonAdded = lodash.cloneDeep(ret)
    ;(reasonAdded as any[]).forEach((item, i) => item.reason = reasons[i])
    console.log(reasonAdded)
    return reasonAdded as WithReason[]
  }

  public recommandQuantity(location: number, chain: OrderItemModel[], consumer: ContactModel | undefined): QuantityWithWeight[] {
    console.log(`recommandQuantity(location: ${location}, chain: ${chain}, consumer: ${consumer?.id})`)
    // console.log('chain', chain.map(item => `${item.name}|${item.unit}|${item.unitPrice}`).join('\n'))

    const rqItem = chain[0]
    // console.log('rqItem', `${rqItem.name}|${rqItem.unit}|${rqItem.unitPrice}`)

    const getQuantity = (order: OrderModel, i: number): number => {
      const item = order.items[i]
      if (!item || !sameItem(item, rqItem)) {
        // if (item) {
        //   console.log('not same')
        //   console.log()
        //   console.log(`${rqItem.name}|${rqItem.unit}|${rqItem.unitPrice}`)
        // }
        return 0
      }
      return item.quantity
    }

    const getRank = (
      order: OrderModel,
      location: number,
      chain: OrderItemModel[],
      consumer: ContactModel | undefined,
    ): number => {
      const sameConsumerFactor = order.consumer && order.consumer?.id === consumer?.id ? 100000000 : 1

      let chainFactor = 1

      // 从1开始，因为0是推荐的物品
      for (let i = 1; i < chain.length; i++) {
        const chainItem = chain[i]
        const orderItem = order.items[location - i]
        if (
          sameItem(orderItem, chainItem)
          && orderItem.quantity === chainItem.quantity
        ) {
          chainFactor *= 10
        }
        else {
          break
        }
      }

      return sameConsumerFactor * chainFactor
    }

    // 对所有订单进行打分
    class Ranked extends OrderModel {
      rank = 0
    }

    this.allOrders.forEach((order) => {
      (order as any).rank = getRank(order, location, chain, consumer)
    })

    const rankedArr = this.allOrders as Ranked[]

    // 对订单的推荐商品进行分组
    const mapItemRankedArr = lodash.groupBy(rankedArr, ranked => getQuantity(ranked, location))

    const quantityWeighted = Object.keys(mapItemRankedArr).map((key) => {
      const quantity = Number.parseFloat(key)
      const weight = mapItemRankedArr[key].map(x => x.rank).reduce((y, x) => y + x, 0)
      return {
        quantity, weight,
      }
    }).filter(x => x.weight !== 0 && x.quantity !== 0)

    console.log(
      mapItemRankedArr,
    )

    quantityWeighted.sort((a, b) => b.weight - a.weight)

    console.log('recommandQuantity')
    console.log(quantityWeighted.map(x => `${x.quantity}(weight: ${x.weight})`).join('\n'))

    return quantityWeighted.filter(x => x.quantity > 0)
  }
}
